package com.volatileshooter.state;

import java.util.List;
import java.util.Random;

import org.lwjgl.input.Keyboard;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Circle;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.geom.Vector2f;
import org.newdawn.slick.state.StateBasedGame;
import org.newdawn.slick.tiled.TiledMap;

import com.volatileshooter.entity.Bucket;
import com.volatileshooter.entity.Entity;
import com.volatileshooter.entity.EntityManager;
import com.volatileshooter.entity.blackhole.BlackHoleEntity;
import com.volatileshooter.entity.bullets.BulletBucketManager;
import com.volatileshooter.entity.bullets.BulletEntity;
import com.volatileshooter.entity.player.PlayerBucketManager;
import com.volatileshooter.entity.player.PlayerController;
import com.volatileshooter.entity.player.PlayerEntity;
import com.volatileshooter.state.command.CommandParser;
import com.volatileshooter.state.command.Output;
import com.volatileshooter.state.command.commands.Bind;
import com.volatileshooter.state.command.commands.Exit;
import com.volatileshooter.state.command.commands.Help;
import com.volatileshooter.state.input.KeyboardEvent;
import com.volatileshooter.state.input.MouseEvent;

import de.lessvoid.nifty.EndNotify;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.controls.console.ConsoleControl;
import de.lessvoid.nifty.effects.EffectEventId;
import de.lessvoid.nifty.elements.Element;
import de.lessvoid.nifty.input.NiftyInputEvent;
import de.lessvoid.nifty.input.mapping.Default;
import de.lessvoid.nifty.screen.KeyInputHandler;
import de.lessvoid.nifty.screen.Screen;
import de.lessvoid.nifty.screen.ScreenController;
import de.lessvoid.nifty.slick.NiftyGameState;

/**
 * GameState.
 * 
 * @author void
 */
public class GameState extends NiftyGameState implements ScreenController, KeyInputHandler {
	/**
	 * myId.
	 */
	public static int id = StateIdProvider.getNextId();

	/**
	 * shapes
	 */
	private Shape playerShape;
	private Shape bulletShape;
	private Shape blackHole;

	/**
	 * input
	 */
	private KeyboardEvent keyboard;
	private MouseEvent mouse;

	/**
	 * just another hack to get back to main menu.
	 */
	private boolean backToMenuPressed = false;

	/**
	 * main menu id.
	 */
	private int mainMenuId;

	/**
	 * container.
	 */
	private GameContainer container;

	/**
	 * crosshair.
	 */
	private Image crosshair;
	private Image ship;

	/**
	 * the map.
	 */
	private TiledMap map;

	/**
	 * the player.
	 */
	private PlayerEntity player;

	/**
	 * world min and max in pixels.
	 */
	private Vector2f mapPos;

	/**
	 * max world pos.
	 */
	private Vector2f maxWorldPos;

	/**
	 * min x.
	 */
	private float minX;

	/**
	 * max x.
	 */
	private float maxX;

	/**
	 * min y.
	 */
	private float minY;

	/**
	 * max y.
	 */
	private float maxY;

	/**
	 * current nifty screen.
	 */
	private Screen screen;

	private CommandParser commandParser;

	/**
	 * Create new GameState.
	 * 
	 * @param newMainMenuId
	 *             main menu id
	 */
	public GameState(final int newMainMenuId) {
		super(newMainMenuId);
		this.commandParser = setUpCommands();
		this.mainMenuId = newMainMenuId;
		fromXml("console.xml", this);
	}

	/**
	 * set up commands.
	 * @return CommandParser instance
	 */
	private CommandParser setUpCommands() {
		CommandParser commands = new CommandParser();
		commands.registerCommand("bind", new Bind());
		commands.registerCommand("exit", new Exit(this));
		commands.registerCommand("help", new Help(commands));
		commands.registerCommand("?", new Help(commands));
		return commands;
	}

	/**
	 * get id.
	 */
	public int getID() {
		return id;
	}

	@Override
	public void init(final GameContainer container, StateBasedGame arg1) throws SlickException {
		this.container = container;
		this.container.setMouseGrabbed(true);

		this.crosshair = new Image("crosshair.png");
		this.ship = new Image("ship_blue.png");
		
		this.map = new TiledMap("resources/tilemap.tmx");
		mapPos = new Vector2f(0, 0);
		maxWorldPos = new Vector2f(
				map.getWidth() * map.getTileWidth() - container.getWidth(),
				map.getHeight() * map.getTileHeight() - container.getHeight());
		minX = 200;
		maxX = container.getWidth() - 200;
		minY = 200;
		maxY = container.getHeight() - 200;
		
		// add buckets
		EntityManager.instance().addBucket("bullets", new BulletBucketManager());
		EntityManager.instance().addBucket("blackholes", null);
		Bucket players = EntityManager.instance().addBucket("players", new PlayerBucketManager());

		// create player entity
		player = new PlayerEntity();
		players.getEntities().add(player);

		// input
		keyboard = new KeyboardEvent();
		mouse = new MouseEvent();

		// shapes
		playerShape = new Circle(0, 0, 5);
		bulletShape = new Circle(0, 0, 1.5f);
		blackHole = new Circle(0, 0, 10);
	}

	@Override
	public void enter(GameContainer container, StateBasedGame game) throws SlickException {
		backToMenuPressed = false;
	}

	@Override
	public void render(GameContainer container, StateBasedGame game, Graphics g) throws SlickException {
		renderBackground(player.getPosition());

		container.getGraphics().setBackground(Color.lightGray);

		g.drawString("Press 1 to add a random black hole", 50, 50);
		g.drawString("Press 2 to remove the last hole", 50, 60);
		g.drawString("Press 3 to change player controller", 50, 70);

		renderBullets(g);
		renderPlayers(g);
		renderBlackHoles(g);
		renderCrossHair(g);

		super.render(container, game, g);
	}

	/**
	 * Render Background.
	 * @param playerPos player position
	 */
	private void renderBackground(final Vector2f playerPos) {
		map.render((int)-mapPos.getX(), (int)-mapPos.getY());
	}

	/**
	 * Render Cross Hair where the mouse points.
	 * 
	 * @param g
	 *             Graphics
	 */
	private void renderCrossHair(final Graphics g) {
		g.setColor(Color.green);
		g.drawImage(crosshair, mouse.getX() - crosshair.getWidth() / 2, mouse.getY() - crosshair.getHeight() / 2);
	}

	private void renderBlackHoles(Graphics g) {
		Bucket holesBucket = EntityManager.instance().getBucket("blackholes");
		for (int i = holesBucket.getEntities().size(); --i >= 0;) {
			BlackHoleEntity entity = (BlackHoleEntity) holesBucket.getEntities().get(i);
			blackHole.setCenterX(entity.getPosition().x);
			blackHole.setCenterY(entity.getPosition().y);

			g.setColor(Color.black);
			g.fill(blackHole);
		}
	}

	private void renderPlayers(Graphics g) {
		Bucket playerBucket = EntityManager.instance().getBucket("players");
		for (int i = playerBucket.getEntities().size(); --i >= 0;) {
			PlayerEntity entity = (PlayerEntity) playerBucket.getEntities().get(i);
			// System.out.println(entity.getPosition().getX());
			Vector2f pos = (new Vector2f(entity.getPosition()));
			pos.sub(mapPos);
			if (pos.getX() > maxX) {
				mapPos.add(new Vector2f(pos.getX() - maxX, 0));
				if (mapPos.getX() > maxWorldPos.getX()) {
					mapPos.set(maxWorldPos.getX(), mapPos.getY());
					entity.getPosition().set(maxWorldPos.getX(), entity.getPosition().getY());
				}
				pos.set(maxX, pos.getY());
			} else if (pos.getX() < minX) {
				mapPos.sub(new Vector2f(minX - pos.getX(), 0));
				if (mapPos.getX() < 0) {
					mapPos.set(0, mapPos.getY());
				}
				pos.set(minX, pos.getY());
			}
			if (pos.getY() > maxY) {
				mapPos.add(new Vector2f(0, pos.getY() - maxY));
				if (mapPos.getY() > maxWorldPos.getY()) {
					mapPos.set(mapPos.getX(), maxWorldPos.getY());
				}
				pos.set(pos.getX(), maxY);
			} else if (pos.getY() < minY) {
				mapPos.sub(new Vector2f(0, minY - pos.getY()));
				if (mapPos.getY() < 0) {
					mapPos.set(mapPos.getX(), 0);
				}
				pos.set(pos.getX(), minY);
			}
			playerShape.setCenterX(pos.getX());
			playerShape.setCenterY(pos.getY());
			g.rotate(playerShape.getCenterX(), playerShape.getCenterY(), entity.getAngle());
			g.drawImage(ship, playerShape.getCenterX() - ship.getWidth()/2, playerShape.getCenterY() - ship.getHeight()/2);
			g.resetTransform();
		}
	}

	private void renderBullets(Graphics g) {
		Bucket bulletBucket = EntityManager.instance().getBucket("bullets");
		for (int i = bulletBucket.getEntities().size(); --i >= 0;) {
			BulletEntity entity = (BulletEntity) bulletBucket.getEntities().get(i);
			bulletShape.setCenterX(entity.getPosition().x);
			bulletShape.setCenterY(entity.getPosition().y);
			g.setColor(Color.red);
			g.fill(bulletShape);
		}
	}

	@Override
	public void update(GameContainer container, StateBasedGame game, int delta) throws SlickException {
		if (backToMenuPressed) {
			game.enterState(mainMenuId);
			return;
		}
		EntityManager.instance().fireEvent(keyboard);
		EntityManager.instance().fireEvent(mouse);
		EntityManager.instance().update(0.1f);
	}

	@Override
	public void keyPressed(int key, char c) {
		super.keyPressed(key, c);
		final Element console = screen.findElementByName("console");
		if (!console.isVisible()) {
			if (key == Input.KEY_1) {
				Random random = new Random();
				BlackHoleEntity blackhole1 = new BlackHoleEntity(new Vector2f(random.nextFloat() * 1024, random
						.nextFloat() * 768), 20f);
				EntityManager.instance().getBucket("blackholes").getEntities().add(blackhole1);
			} else if (key == Input.KEY_2) {
				List<Entity> entities = EntityManager.instance().getBucket("blackholes").getEntities();
				if (entities.size() > 0)
					entities.remove(entities.size() - 1);
			} else if (key == Input.KEY_3) {
				List<Entity> entities = EntityManager.instance().getBucket("players").getEntities();
				for (int i = entities.size(); --i>= 0; ) {
					entities.get(i).getControllers().clear();
					entities.get(i).getControllers().add(new PlayerController((PlayerEntity)entities.get(i)));
				}
			}
			keyboard.setKeyDown(key);
		}
	}

	@Override
	public void keyReleased(int key, char c) {
		keyboard.setKeyReleased(key);

		if (key == Keyboard.KEY_ESCAPE) {
			notifyExit();
		}
	}

	@Override
	public void mouseMoved(int oldx, int oldy, int newx, int newy) {
		mouse.setX(newx);
		mouse.setY(newy);
		mouse.setDX(newx - oldx);
		mouse.setDY(newy - oldy);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.newdawn.slick.state.BasicGameState#mousePressed(int, int, int)
	 */
	@Override
	public void mousePressed(int button, int x, int y) {
		mouse.setMouseButtonDown(button);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.newdawn.slick.state.BasicGameState#mouseReleased(int, int, int)
	 */
	@Override
	public void mouseReleased(int button, int x, int y) {
		mouse.setMouseButtonDown(-1);
	}

	/**
	 * Bind this ScreenController to a screen.
	 * @param newNifty nifty
	 * @param newScreen screen
	 */
	public void bind(final Nifty newNifty, final Screen newScreen) {
		screen = newScreen;
		screen.addKeyboardInputHandler(new Default(), this);

		final Element console = screen.findElementByName("console");
		console.hide();

		final ConsoleControl consoleControl = console.getControl(ConsoleControl.class);
		consoleControl.addCommandHandler(commandParser);
		commandParser.setOutput(new Output() {
			public void write(final String output) {
				consoleControl.output(output);
			}
		});
	}
	
	/**
	 * on start screen.
	 */
	public void onStartScreen() {
	}

	/**
	 * on end screen.
	 */
	public void onEndScreen() {
	}
	
	/**
	 * process a keyEvent for the whole screen.
	 * @param inputEvent the input event
	 * @return true when consumen and false when not
	 */
  public boolean keyEvent(final NiftyInputEvent inputEvent) {
    if (inputEvent == NiftyInputEvent.ConsoleToggle) {
      final Element console = screen.findElementByName("console");
      if (!console.isVisible()) {
        showConsole();
      } else {
        hideConsole(new EndNotify() {
			          public void perform() {
			            console.hide();
			          }
			        });
      }
      return true;
    } else {
      return false;
    }
  }

  /**
   * show console.
   */
	private void showConsole() {
	    final Element console = screen.findElementByName("console");
		console.show();
	    console.setAlternateKey("show");
	    console.startEffect(
	        EffectEventId.onCustom,
	        new EndNotify() {
	          public void perform() {
	            console.setFocus();
	          }
	        });
	}

	/**
	 * hide console.
	 * @param endNotify TODO
	 */
	private void hideConsole(final EndNotify endNotify) {
	    final Element console = screen.findElementByName("console");
		console.setAlternateKey("hide");
	    console.startEffect(
	        EffectEventId.onCustom,
	        endNotify);
	}

  /**
   * notify exit.
   */
  public void notifyExit() {
	  final Element console = screen.findElementByName("console");
	  hideConsole(new EndNotify() {
		          public void perform() {
		            console.hide();
		            backToMenuPressed = true;
		          }
		        });
  }
}
